// pcselfy.cpp : Implementation of CPCSelfy

#include "stdafx.h"

#include <list>
#include <utility>
#include <atlfile.h>

#include "utility.h"
#include "pcselfy.h"

extern ATL::CWin32Heap _Win32Heap;

void TOVERLAP::Free(HANDLE pointer)
{
	if (pointer) _Win32Heap.Free(pointer);
}

//	struct DECLSPEC_UUID("897215A7-5100-478C-BF27-3B6B1160F9F6") CoPCSelfy;
//	OBJECT_ENTRY_AUTO(__uuidof(CoPCSelfy), CPCSelfy)
typedef std::pair<TOVERLAP, TOVERLAP> TDOUBLE;

LPCWSTR szTrimChars = L"\n\t\r\x20";
LPCWSTR szNone		= L"NnFfZz";
LPCWSTR szComplex	= L",()[]{}";

const unsigned BMP_HEADER_SIZE = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << 20);

inline void reportError(DWORD acode, LPCWSTR aprefix)
{
	CComBSTR suffix(aprefix);
		suffix.Append(L" HRESULT 0x%8.8x .");
	CComBSTR thestr;	// L"HRESULT 0x%8.8x", 
	if(NotAtlGetErrorDescription(acode, suffix, & thestr))
	AtlReportError(IID_IPCSelfy, thestr);
}

void CALLBACK procFileIOCompletion(DWORD dwErrorCode, DWORD anumbytes, LPOVERLAPPED alp)
{
	LPBYTE pointer = (LPBYTE) alp->hEvent;
	if (pointer)	//	if (dwErrorCode ^ ERROR_IO_PENDING)
	try {
		pointer = (LPBYTE) ::InterlockedExchangePointer(& (alp->hEvent), NULL);
		if (pointer) _Win32Heap.Free(pointer);
	}
	catch(...) {}
}

// CPCSelfy
POINT	CPCSelfy::g_nnCursor;

STDMETHODIMP CPCSelfy::save(BSTR apathname, VARIANT anopt, VARIANT_BOOL* pfullscreen)
{
	if (!(apathname)) return E_POINTER;
//	TODO:  	copy to clipboard 
	::StrTrimW(apathname, szTrimChars);
	if (!(* apathname)) return E_NOTIMPL; // return E_INVALIDARG;

	CAtlFile thefile;
	HRESULT hRes = thefile.Create( apathname, 
									GENERIC_WRITE, 
									FILE_SHARE_READ, 
									CREATE_ALWAYS,
					FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED );
    if (SUCCEEDED(hRes)) 
	hRes = screen2file( thefile.operator HANDLE(), anopt, pfullscreen );
	if (SUCCEEDED(hRes))
		if (::SleepEx(0, TRUE) != WAIT_IO_COMPLETION)
			::SleepEx(0, TRUE);	// return E_PENDING;
	return hRes;
}

HRESULT CPCSelfy::screen2file(HANDLE afile, VARIANT anopt, VARIANT_BOOL* pfullscreen)
{
	::VariantCopyInd(& anopt, & anopt);

	VARIANT_BOOL thefull = VARIANT_FALSE; // 0
	bool micemode = true;
	if ((anopt.vt ^ VT_ERROR) && (anopt.vt >> 1)) // not VT_EMPTY nor VT_NULL
	{
		CComBSTR thestr;
		CComVariant thevt;
		if ((anopt.vt == VT_BSTR) && (anopt.bstrVal))
		{
			::StrTrimW(anopt.bstrVal, szTrimChars);
			thestr = anopt.bstrVal;
		}
		else {
			::VariantChangeType(& thevt, & anopt, VARIANT_NOVALUEPROP, VT_I4);
			if (thevt.vt ^ VT_I4) 
			{
				::VariantChangeType(& thevt, & anopt, VARIANT_NOVALUEPROP, VT_BSTR);
				thestr = thevt.bstrVal;
			}
		}

		int thelen = thestr.Length();
		if (thelen)
		{
			if (::StrCSpnW(thestr, szComplex) < thelen) return E_NOTIMPL;
			if (::StrCSpnW(thestr, szTrimChars) < thelen) return E_NOTIMPL;

			thevt = CComVariant(long(0), VT_I4);
			HRESULT hRes = ::VarI4FromStr( thestr, 
											LOCALE_SYSTEM_DEFAULT,
											0, & (thevt.lVal) );
			if (hRes ^ S_OK)
				if (::StrCSpnW(thestr, szNone) / thelen) 
					thevt.lVal = VARIANT_TRUE;
		}

		//	IMPL ONLY FULL SCREEN
		if (thevt.vt ^ VT_I4) return HRESULT_FROM_WIN32(ERROR_INVALID_VARIANT);

		if (thevt.lVal) thefull = VARIANT_TRUE;
	}

	POINT thoffest = { 0, 0 };
	POINT thesize = m_nnSize;
	if (thefull)
	{	
		thesize.x = ::GetDeviceCaps(m_hdcInfo, HORZRES);
		thesize.y = ::GetDeviceCaps(m_hdcInfo, VERTRES);
	}
	else
		if(::GetCursorPos(& thoffest))
		{
			thoffest.x -= m_nnSize.x / 2;
			thoffest.y -= m_nnSize.y / 2;
			cutTopLeft(& thesize, & thoffest);
		}

	HRESULT hRes = captureScreen( afile, thoffest, thesize );
	if (pfullscreen) * pfullscreen = thefull;
	return hRes;
}

/*
	if (ahandle.vt == VT_DISPATCH) 
	{
		thevt = L"abracadabra";
		SAFEARRAY * psa = NULL;
		hRes = ::VectorFromBstr( thevt.bstrVal, & psa );
		if (SUCCEEDED(hRes))
		{
			thevt = psa;
			IDispatch * pDisp = ahandle.pdispVal;
		//	dispinterface _Stream { ("ADODB.Stream") }
		//	[id(0x0000000d), helpcontext(0x0012ccbe)]
		//	HRESULT Write([in] VARIANT Buffer);
			DISPPARAMS	param = { & thevt, NULL, 1, 0 };
			hRes = pDisp->Invoke( 0xd, IID_NULL, 
						LOCALE_SYSTEM_DEFAULT,
						DISPATCH_METHOD,
						& param, NULL, 
						NULL, NULL); 
			if (SUCCEEDED(hRes)) return hRes;
		}	*/

STDMETHODIMP CPCSelfy::load(VARIANT ahandle, VARIANT anopt, VARIANT_BOOL* pfullscreen)
{
	::VariantCopyInd(& ahandle, & ahandle);
	if (ahandle.vt == VT_DISPATCH) return E_NOTIMPL;

	CComVariant thevt;
	HRESULT hRes = ::VariantChangeType(& thevt, & ahandle, VARIANT_NOVALUEPROP, VT_INT);
	if (FAILED(hRes)) return hRes;

	int theval = thevt.intVal;
	if (!theval || !(theval + 1))
		return E_INVALIDARG;
//	if (!(theval >> 4)) // file descriptor
	if(::GetFileSize((HANDLE) theval, NULL) ^ INVALID_FILE_SIZE)
	{
		hRes = screen2file((HANDLE) theval, anopt, pfullscreen);
		::SleepEx(0, TRUE);
	}
	else {
		reportError(::GetLastError(), L"Method: load, defect: first argument.");
		hRes = E_HANDLE;
	}
//		else hRes = E_NOTIMPL;
	return hRes;
}

HRESULT CPCSelfy::FinalConstruct() 
{ 
	m_hdcInfo = ::CreateICW(L"DISPLAY", NULL, NULL, NULL);
	if (!m_hdcInfo) HRESULT_FROM_WIN32(::GetLastError());

		g_nnCursor.x = (::GetSystemMetrics(SM_CXCURSOR) >> 2);
		g_nnCursor.y = (::GetSystemMetrics(SM_CYCURSOR) >> 2);

	return S_OK;
}

void CPCSelfy::FinalRelease() 
{ 
	try {
		::DeleteDC(m_hdcInfo);
		m_lstMem.clear();
	}
	catch(...) {}
}

inline unsigned getBmpSize(PBITMAPINFO pbi)
{
	PBITMAPINFOHEADER pbih = (PBITMAPINFOHEADER) pbi; 
	return pbih->biSizeImage;
}

HBITMAP CPCSelfy::CreateBitmap(const POINT & asize)
{
	if (!m_nBitsPixel) return ::CreateCompatibleBitmap(m_hdcInfo, asize.x, asize.y);

	BITMAPINFO thebmi;
	::memset(& thebmi, 0, sizeof(BITMAPINFO));
		thebmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		thebmi.bmiHeader.biWidth = asize.x;
		thebmi.bmiHeader.biHeight = asize.y;
		thebmi.bmiHeader.biPlanes = 1; // the number of planes for the target device.
		thebmi.bmiHeader.biBitCount = m_nBitsPixel; // (16, 24, 32)
		thebmi.bmiHeader.biCompression = BI_RGB; // An uncompressed format.
	return ::CreateDIBSection( m_hdcInfo, 
								& thebmi, 
							DIB_RGB_COLORS, // The BITMAPINFO structure contains an array of literal RGB values.
								NULL, NULL, 0 );
}

HRESULT CPCSelfy::captureScreen(HANDLE afile, const POINT & anoffset, const POINT & asize)
{
	DWORD dwCode(0);
	HDC hdcMemDC = ::CreateCompatibleDC(m_hdcInfo);
		if (!hdcMemDC) dwCode = ::GetLastError();

	HBITMAP hBitmap = CreateBitmap(asize);
		if (!hBitmap) dwCode = ::GetLastError();

	if (!hdcMemDC || !hBitmap)
	{
		if (hBitmap) ::DeleteObject(hBitmap);
		if (hdcMemDC) ::DeleteDC(hdcMemDC);
		if (!dwCode) dwCode = ERROR_MONITOR_NO_DESCRIPTOR;
		return HRESULT_FROM_WIN32(dwCode);
	}

	HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(hdcMemDC, hBitmap);      

	HRESULT hRes = S_OK;
	BOOL result = FALSE;
	HDC hdcScreen = ::GetDC(NULL);
	if (hdcScreen)
	{
		CURSORINFO theinfo = { sizeof(CURSORINFO), 0, NULL };
		result = ::BitBlt( hdcMemDC, 0, 0, 
							asize.x, asize.y, 
							hdcScreen, 
							anoffset.x, anoffset.y,
							SRCCOPY | CAPTUREBLT );
			hRes = HRESULT_FROM_WIN32(::GetLastError());
		::ReleaseDC(0, hdcScreen);

		if (result && m_bCursor)
		if (::GetCursorInfo( & theinfo ))
		{
			RECT therect = { 0, 0, asize.x, asize.y };
			POINT point = theinfo.ptScreenPos;
				point.x -= (anoffset.x + g_nnCursor.x);
				point.y -= (anoffset.y + g_nnCursor.y);
			if (::PtInRect(& therect, point))
			::DrawIcon( hdcMemDC, point.x, point.y, theinfo.hCursor );
		}
	}
	else 
		hRes = HRESULT_FROM_WIN32(::GetLastError());

	if (result)
	try {
		hRes = S_OK;
		hBitmap = (HBITMAP) ::SelectObject(hdcMemDC, hOldBitmap);
		BITMAP thebmp; // Retrieve the bitmap color format, width, and height.  
		CComBSTR thebstr(BMP_HEADER_SIZE + 1);
		if (::GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &thebmp))
		{
			PBITMAPINFO pbmi = (PBITMAPINFO) thebstr.operator LPWSTR();
			CreateBitmapInfoStruct(thebmp, pbmi);

			LPBYTE lpBits = (LPBYTE) _Win32Heap.Allocate(getBmpSize(pbmi));
			if (lpBits)
			{
 // Retrieve the color table (RGBQUAD array) and the bits (array of palette indices) from the DIB.  
				if (bmp2mem(pbmi, hBitmap, lpBits, m_hdcInfo))
					if (CreateBMPFile( afile, pbmi, lpBits ))
						lpBits = NULL;
					else hRes = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
				else hRes = E_INVALIDARG;
				if (lpBits) _Win32Heap.Free(lpBits);
			}
				else hRes = E_OUTOFMEMORY;
		}
			else hRes = HRESULT_FROM_WIN32(::GetLastError());
	}
	catch(...) 
	{
		hRes = E_OUTOFMEMORY;
	}

	::DeleteObject(hBitmap);
	::DeleteDC(hdcMemDC);
	return hRes;
}

STDMETHODIMP CPCSelfy::get_cursor(VARIANT_BOOL* pVal)
{
	// TODO: Add your implementation code here
	if (!pVal) return E_POINTER;
		* pVal = m_bCursor;
	return S_OK;
}

STDMETHODIMP CPCSelfy::put_cursor(VARIANT_BOOL newVal)
{
	if (m_bCursor = newVal)
	{
		g_nnCursor.x = (::GetSystemMetrics(SM_CXCURSOR) >> 2);
		g_nnCursor.y = (::GetSystemMetrics(SM_CYCURSOR) >> 2);
	}
	return S_OK;
}

STDMETHODIMP CPCSelfy::get_pixelBits(SHORT* pVal)
{
	if (!pVal) return E_POINTER;
	if (m_nBitsPixel) * pVal = m_nBitsPixel;
		else * pVal = ::GetDeviceCaps(m_hdcInfo, BITSPIXEL);
	return S_OK;
}

STDMETHODIMP CPCSelfy::put_pixelBits(SHORT newVal)
{
	static const unsigned pl2one[] = { 0, 16, 16, 24, 32 };
		unsigned index = ::abs(newVal >> 3);
	m_nBitsPixel = (index >> 2) ? 32 : pl2one[index];
	return S_OK;
}

STDMETHODIMP CPCSelfy::get_height(LONG* pVal)
{
	if (!pVal) return E_POINTER;
	* pVal = m_nnSize.y;
	return S_OK;
}

STDMETHODIMP CPCSelfy::get_width(LONG* pVal)
{
	if (!pVal) return E_POINTER;
	* pVal = m_nnSize.x;
	return S_OK;
}

STDMETHODIMP CPCSelfy::get_length(SHORT* pVal)
{
	if (!pVal) return E_POINTER;
	::SleepEx(0, TRUE);
	try {
		* pVal = m_lstMem.size();
	}
	catch(...) 
	{
		return E_FAIL;
	}

	if ((* pVal) < 0) return TYPE_E_SIZETOOBIG;
	return S_OK;
}

STDMETHODIMP CPCSelfy::put_height(LONG newVal)
{
	newVal = ::abs(newVal);
	if (newVal > ::GetSystemMetrics(SM_CYSCREEN))
		newVal = ::GetSystemMetrics(SM_CYSCREEN) / 2;
	else 
	if (newVal < g_nnCursor.y) newVal = g_nnCursor.y * 2;
	if (!newVal) return E_INVALIDARG;

	m_nnSize.y = newVal;
	return S_OK;
}

STDMETHODIMP CPCSelfy::put_width(LONG newVal)
{
	newVal = ::abs(newVal);
	if (newVal > ::GetSystemMetrics(SM_CXSCREEN))
		newVal = ::GetSystemMetrics(SM_CXSCREEN) / 2;
	else 
	if (newVal < g_nnCursor.x) newVal = g_nnCursor.x * 2;
	if (!newVal) return E_INVALIDARG;

	m_nnSize.x = newVal;
	return S_OK;
}

bool CPCSelfy::bmp2mem(PBITMAPINFO pbi, HBITMAP hBMP, LPBYTE lpBits, HDC anhdc)
{
    PBITMAPINFOHEADER pbih = (PBITMAPINFOHEADER) pbi; 
	int thecount = ::GetDIBits( anhdc, hBMP, 0, 
					(WORD) pbih->biHeight, 
						lpBits, pbi, 
						DIB_RGB_COLORS);
//			hRes = HRESULT_FROM_WIN32(::GetLastError());
	if (thecount < pbih->biHeight) 
			reportError(::GetLastError(), L"Win32 GetDIBits,");
		else return true;
	return false;
}

bool CPCSelfy::CreateBMPFile(HANDLE afile, PBITMAPINFO pbi, LPBYTE lpBits)
{ 
	// bitmap info-header  
    PBITMAPINFOHEADER pbih = (PBITMAPINFOHEADER) pbi; 
    BITMAPFILEHEADER thehdr;       // bitmap file-header  
		::memset(& thehdr, 0, sizeof(BITMAPFILEHEADER));
	//    BITMAPFILEHEADER hdr;       // bitmap file-header  
		thehdr.bfType = 0x4d42;        // 0x42 = "B" 0x4d = "M"  
    // Compute the size of the entire file.  
		thehdr.bfSize = (DWORD) ( pbih->biSize + 
							sizeof(BITMAPFILEHEADER) + 
							pbih->biClrUsed * sizeof(RGBQUAD) + 
							pbih->biSizeImage ); 

    // Compute the offset to the array of color indices.  
		thehdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + 
				pbih->biSize + pbih->biClrUsed * sizeof (RGBQUAD); 

    // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.  
	//ORD dwTotal = setBMPHeader(pbih, & hdr);
    DWORD dwTotal = (sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD));

	DWORD dwCode; // or lpNumberOfBytesWritten
	{
	TDOUBLE	thepair(TOVERLAP(0), TOVERLAP(sizeof(BITMAPFILEHEADER)));
	// Copy the BITMAPFILEHEADER into the .BMP file.  
	thepair.first.m_bResult = ::WriteFile(  afile, 
										 (LPVOID) & thehdr, 
										  sizeof(BITMAPFILEHEADER), 
											& dwCode,
										  & (thepair.first) );
		dwCode = ::GetLastError();
	if (!(thepair.first.m_bResult))
		if(dwCode ^ ERROR_IO_PENDING)
		{
			reportError(dwCode, L"Writes BMP header,");
			return false;
		}
    // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.  
	thepair.second.m_bResult = ::WriteFile(  afile, 
										(LPVOID) pbih, 
										 dwTotal, 
											& dwCode,
										 & (thepair.second) );
		dwCode = ::GetLastError();
	if (!(thepair.second.m_bResult))
		if(dwCode ^ ERROR_IO_PENDING)
		{
			reportError(dwCode, L"Writes BMP header,");
			return false;
		}
//		if (!dwCode) dwCode = ERROR_WRITE_FAULT;
		::SleepEx(0, TRUE);
		if(dwCode == ERROR_IO_PENDING)
			if (thepair.first.testResult(afile))
				if (thepair.second.testResult(afile, TRUE)) dwCode = 0;
					else dwCode = thepair.second.getCode();
			else dwCode = thepair.first.getCode();
		 else dwCode = 0;
	}	//	TDOUBLE	thepair

	if (dwCode)
	{
		reportError(dwCode, L"Writes BMP header,");
		return false;
	}	// count of bytes of double ops
		else dwTotal += sizeof(BITMAPFILEHEADER); 

	TOVERLAP	theoverlap(dwTotal);
//	TSTDLIST::reverse_iterator index = m_lstMem.rbegin();
	TSTDLIST::iterator index = m_lstMem.insert(m_lstMem.end(), theoverlap);
	LPOVERLAPPED pOverLap = & (*index);

	BOOL result = ::WriteFileEx( afile, lpBits,	//	Pointer to the buffer containing the data to be written to the file.
					pbih->biSizeImage,	//	Number of bytes to be written to the file.
					pOverLap,	// OVERLAPPED structure supplies data to be used during the overlapped (asynchronous) write operation. 
					procFileIOCompletion );

	if (result) 
	{
		index->hEvent = lpBits;
		::QueueUserAPC( procAPCgrabber, ::GetCurrentThread(), (ULONG_PTR) & m_lstMem );
	}
	else {
		dwCode = ::GetLastError();
		m_lstMem.erase(index);
		reportError(dwCode, L"Writes BMP pixels,");
		return false;
	}
	
	return true;
}

void CALLBACK CPCSelfy::procAPCgrabber(ULONG_PTR dwParam)
{
	TSTDLIST * plist = (TSTDLIST *) dwParam;
	if (dwParam)
	try {
		TSTDLIST::iterator index = plist->begin();
		for ( ; index != plist->end(); )
			if (index->Offset && index->hEvent) ++index;
			else index = plist->erase(index);
	}
	catch(...) {}
}
